﻿Imports OpenTK.Graphics.OpenGL
Imports System.Deployment
Imports OpenTK.Math
Imports System.IO
Imports System.Math

Public Class MODViewer
    Public Shared loadedModels(-1) As Model
    Dim MatEditor As MaterialEditor
    Dim Camera As New OpenGLCamera()
    Dim KBState As KeyStatus
    Dim MouseState As MouseStatus
    Dim renderMode As BeginMode = BeginMode.Triangles
    Dim curTool As MouseTools = MouseTools.NoTool
    Dim w As Integer = Renderer.Width
    Dim h As Integer = Renderer.Height
    Dim modCnt As Integer = 0
    Dim mfn As String = ""
    Dim pA As UInteger = 0
    Dim pB As UInteger = 0
    Dim pC As UInteger = 0
    Dim cSpB As Boolean = False
    Dim mP As Point
    Dim nP As Point
    Dim pause As Boolean = False
    Dim AppDone As Boolean = False
    Dim ds As Boolean = False
    Dim mPix(2) As Byte
    Dim vP(3) As Integer
    Private Sub MainExecute()

        GL.Clear(ClearBufferMask.DepthBufferBit Or ClearBufferMask.ColorBufferBit)

        With MouseState
            If .change Then
                .newPos = Renderer.PointToClient(Windows.Forms.Cursor.Position)
                Camera.Update(MouseState, Nothing, lockAxes = Axis.X Or lockAxes = Axis.BOTH, _
                                              lockAxes = Axis.Y Or lockAxes = Axis.BOTH)
                UpdateRotLabels()
                If .msRight Then UpdatePosLabels()
            End If
            .oldPos = Renderer.PointToClient(Windows.Forms.Cursor.Position)
        End With


        With KBState
            If (.W Or .S) Or (.D Or .A) Then
                Camera.Update(Nothing, KBState)
                UpdatePosLabels()
            End If
        End With

        GL.PushMatrix()
        GL.LoadIdentity()
        GL.Rotate(Camera.rot.z, 0, 0, 1)
        GL.Rotate(Camera.rot.y, 1, 0, 0)
        GL.Rotate(Camera.rot.x, 0, 1, 0)
        GL.Translate(Camera.pos.x, Camera.pos.y, Camera.pos.z)

        If loadedModels.Length > 0 Then
            For i As Integer = 0 To loadedModels.Length - 1
                If loadedModels(i).Enabled Then loadedModels(i).Draw(renderMode)
            Next
        End If

        GL.PopMatrix()

        Renderer.SwapBuffers()

        If MouseState.change Then
            GL.Disable(EnableCap.Lighting)
            GL.Disable(EnableCap.Texture2D)
            GL.Disable(EnableCap.ColorMaterial)

            GL.PushMatrix()
            GL.LoadIdentity()
            GL.Rotate(Camera.rot.z, 0, 0, 1)
            GL.Rotate(Camera.rot.y, 1, 0, 0)
            GL.Rotate(Camera.rot.x, 0, 1, 0)
            GL.Translate(Camera.pos.x, Camera.pos.y, Camera.pos.z)

            For i As Integer = 0 To loadedModels.Length - 1
                If loadedModels(i).Enabled Then loadedModels(i).drawSelMode(curTool)
            Next

            GL.PopMatrix()

            GL.GetInteger(GetPName.Viewport, vP)
            GL.ReadPixels(MouseState.oldPos.X, vP(3) - MouseState.oldPos.Y, 1, 1, PixelFormat.Rgb, PixelType.UnsignedByte, mPix)

            GL.Enable(EnableCap.Lighting)
            GL.Enable(EnableCap.Texture2D)
            GL.Enable(EnableCap.ColorMaterial)
            GL.Disable(EnableCap.PolygonOffsetFill)
            For i As Integer = 0 To loadedModels(0).ModelData.Parts.Length - 1
                With loadedModels(0).ModelData.Parts(i)
                    If mPix(0) = .PickCol(0) And mPix(1) = .PickCol(1) And mPix(2) = .PickCol(2) Then
                        If MouseState.msRight Then
                            pList.SelectedNode = pList.Nodes(i)
                        End If
                    End If
                End With
            Next

            ds = False
        End If
    End Sub
#Region "I/O Interaction & Information Events"
    Private Sub ImportToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles ImportToolStripMenuItem.Click
        pause = True
        Dim opn As OpenFileDialog = New OpenFileDialog
        opn.FileName = ""
        opn.Filter = "Supported 3D Model Files (obj, 3ds)|*.obj;*.3ds|OBJ Models|*.obj|3DS Models|*.3ds"
        If opn.ShowDialog() = Windows.Forms.DialogResult.OK Then
            ImportModel(opn.FileName)
            mtlEdBtn.Enabled = True
        End If
        pause = False
    End Sub
    Private Sub ImportModel(ByVal fileName As String, Optional ByVal append As Boolean = False)
        mfn = fileName
        Dim fn As String = Path.GetFileName(mfn)
        If append Then
            modCnt += 1
            ReDim Preserve loadedModels(modCnt - 1)
            loadedModels(modCnt - 1) = New Model(mfn)
            AppStatusLabel.Text = "Loading " & fn & " into current scene..."
        Else
            modCnt = 1
            ReDim loadedModels(0)
            loadedModels(0) = New Model(mfn)
            AppStatusLabel.Text = "Loading " & fn & "..."
        End If
        loadPList()
        populateInfo()
        If append Then
            AppStatusLabel.Text = Path.GetFileName(mfn) & " Added to scene!"
        Else
            AppStatusLabel.Text = Path.GetFileName(mfn) & " Loaded!"
        End If
    End Sub
    Private Sub loadPList()
        pList.Nodes.Clear()
        Dim argb(2) As Double
        For i As Integer = 0 To loadedModels(0).ModelData.Parts.Length - 1
            With loadedModels(0).ModelData.Parts(i)
                pList.Nodes.Add(.grpId)
                If .matInd > -1 Then
                    pList.Nodes(i).Nodes.Add("Material ID: " & loadedModels(0).ModelData.Materials(.matInd).mtlId)
                End If
                pList.Nodes(i).Checked = True
            End With
        Next
    End Sub
    Private Sub populateInfo()
        With loadedModels(0)
            Dim fc As Integer = 0
            Dim vc As Integer = 0
            Dim pc As Integer = 0
            For i As Integer = 0 To .ModelData.Parts.Length - 1
                If Not .ModelData.Parts(i).skip Then
                    fc += .ModelData.Parts(i).Faces.Length
                    pc += 1
                End If
            Next
            vc = fc * 3
            vsLabel.Text = "Parts (" & pc & " out of " & .ModelData.Parts.Length & " rendering)"
            vcLabel.Text = "Vertex Count: " & vc
            psLabel.Text = "Polygon Count: " & fc
        End With
    End Sub
    Private Sub pList_ItemCheck(sender As Object, e As ItemCheckEventArgs)
        With loadedModels(0).ModelData
            .Parts(e.Index).skip = (e.NewValue = CheckState.Unchecked)
            populateInfo()
        End With
    End Sub
#End Region
#Region "Application Initialization Events"
    Private Sub MainWindow_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        GL.Enable(EnableCap.Lighting)
        GL.Enable(EnableCap.Light0)
        GL.Enable(EnableCap.Texture2D)
        GL.Enable(EnableCap.Normalize)
        GL.Enable(EnableCap.ColorMaterial)
        GL.ClearColor(Color.DodgerBlue)
        GL.Enable(EnableCap.DepthTest)
        GL.ActiveTexture(TextureUnit.Texture0)
        Me.Show()
        SetDirLock(Axis.BOTH)
        ChangeTool(MouseTools.NoTool)
        Reshape()
        Camera.Speed = (CamSpeedBar.Value / 255.0F)
        RenderOp.Enabled = True
        RenderOp.Start()
    End Sub
#End Region
#Region "Keyboard State Events"
    Private Sub Renderer_KeyUp(sender As Object, e As KeyEventArgs) Handles Renderer.KeyUp
        With KBState
            Select Case e.KeyCode
                Case Keys.W
                    .W = False
                Case Keys.S
                    .S = False
                Case Keys.A
                    .A = False
                Case Keys.D
                    .D = False
            End Select
        End With
    End Sub
    Private Sub Renderer_KeyDown(sender As Object, e As KeyEventArgs) Handles Renderer.KeyDown
        With KBState
            Select Case e.KeyCode
                Case Keys.W
                    .W = True
                Case Keys.S
                    .S = True
                Case Keys.A
                    .A = True
                Case Keys.D
                    .D = True
                Case Keys.R
                    Camera.Reset()
                Case Keys.Oem1
                    ChangeTool(MouseTools.NoTool)
                Case Keys.Oem2
                    ChangeTool(MouseTools.PartSelector)
                Case Keys.Oem3
                    ChangeTool(MouseTools.ModelSelector)
                Case Keys.Oem4
                    ChangeTool(MouseTools.VertexSelector)
            End Select
        End With
    End Sub
#End Region
#Region "Mouse State Events"
    Private Sub Renderer_MouseUp(sender As Object, e As MouseEventArgs) Handles Renderer.MouseUp
        With MouseState
            Select Case e.Button
                Case Windows.Forms.MouseButtons.Left
                    .msLeft = False
                    .change = False
                Case MouseButtons.Right
                    .msRight = False
                    .change = False
            End Select
            Me.Cursor = Cursors.Default
        End With
    End Sub
    Private Sub Renderer_MouseDown(sender As Object, e As MouseEventArgs) Handles Renderer.MouseDown
        With MouseState
            '.oldPos = Renderer.PointToClient(Windows.Forms.Cursor.Position)
            Select Case e.Button
                Case Windows.Forms.MouseButtons.Left
                    SetCursor()
                    .msLeft = True
                    .change = True
                Case MouseButtons.Right
                    SetCursor()
                    .msRight = True
                    .change = True
            End Select
        End With
    End Sub

#End Region
#Region "Viewer Context Events"
    Private Sub MainWindow_Resize(sender As Object, e As EventArgs) Handles MyBase.Resize
        If RenderOp.Enabled Then Reshape()
    End Sub
    Private Sub Reshape()
        w = Renderer.Width
        h = Renderer.Height
        Dim fH As Double = Tan((60.0F / 360.0F) * PI)
        Dim fW As Double = fH * (w / h)
        GL.MatrixMode(MatrixMode.Projection)
        GL.LoadIdentity()
        GL.Frustum(-fW, fW, -fH, fH, 1, 999999)
        GL.Viewport(0, 0, w, h)
        GL.MatrixMode(MatrixMode.Modelview)
    End Sub
#End Region
#Region "UI STUFF"
    Sub UpdateRotLabels()
        cXrLabel.Text = "Rot. Vector X: " & CInt(Camera.rot.x)
        cYrLabel.Text = "Rot. Vector Y: " & CInt(Camera.rot.y)
        cZrLabel.Text = "Rot. Vector Z: " & CInt(Camera.rot.z)
    End Sub
    Sub UpdatePosLabels()
        cXpLabel.Text = "Pos. on X: " & CInt(Camera.pos.x)
        cYpLabel.Text = "Pos. on Y: " & CInt(Camera.pos.y)
        cZpLabel.Text = "Pos. on Z: " & CInt(Camera.pos.z)
    End Sub
    Sub SetCursor()
        If lockAxes = Axis.X Then
            Me.Cursor = Cursors.SizeWE
        ElseIf lockAxes = Axis.Y Then
            Me.Cursor = Cursors.SizeNS
        ElseIf lockAxes = Axis.NONE Then
            Me.Cursor = Cursors.Default
        Else
            Me.Cursor = Cursors.SizeAll
        End If
    End Sub


#End Region
#Region "Menu Interaction"
    Private Sub ChangeTool(Optional ByVal t As MouseTools = MouseTools.NoTool)
        curTool = t
        Select Case t
            Case MouseTools.PartSelector
                ToolLabel.Text = "Mouse Tool: Part Selector"
            Case MouseTools.ModelSelector
                ToolLabel.Text = "Mouse Tool: Model Selector"
            Case MouseTools.VertexSelector
                ToolLabel.Text = "Mouse Tool: Vertex Selector"
            Case MouseTools.NoTool
                ToolLabel.Text = "Mouse Tool: None (camera only)"
            Case MouseTools.PolygonSelector
                ToolLabel.Text = "Mouse Tool: Polygon Selector"
        End Select
    End Sub
    Private Sub PartSel_Click(sender As Object, e As EventArgs)
        ChangeTool(MouseTools.PartSelector)
    End Sub

    Private Sub VertSel_Click(sender As Object, e As EventArgs)
        ChangeTool(MouseTools.VertexSelector)
    End Sub

    Private Sub ModelSel_Click(sender As Object, e As EventArgs)
        ChangeTool(MouseTools.ModelSelector)
    End Sub

    Private Sub NoToolSel_Click(sender As Object, e As EventArgs)
        ChangeTool(MouseTools.NoTool)
    End Sub
    Private Sub PolygonSelectorToolStripMenuItem_Click(sender As Object, e As EventArgs)
        ChangeTool(MouseTools.PolygonSelector)
    End Sub
    Private Sub TrianglesToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles TrianglesToolStripMenuItem.Click
        renderMode = BeginMode.Triangles
        GL.PolygonMode(1032, PolygonMode.Fill)
    End Sub
    Private Sub PointsToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles PointsToolStripMenuItem.Click
        renderMode = BeginMode.Points
        GL.PolygonMode(1032, PolygonMode.Fill)
    End Sub
    Private Sub LinesToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles LinesToolStripMenuItem.Click
        renderMode = BeginMode.Triangles
        GL.PolygonMode(1032, PolygonMode.Line)
    End Sub
    Private Sub MODViewer_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
        AppDone = True
        Me.Dispose()
    End Sub

    Private Sub SelectAllToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles SelectAllToolStripMenuItem.Click
        For i As Integer = 0 To pList.Nodes.Count - 1
            pList.Nodes(i).Checked = True
        Next
    End Sub

    Private Sub DeselectAllToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles DeselectAllToolStripMenuItem.Click
        For i As Integer = 0 To pList.Nodes.Count - 1
            pList.Nodes(i).Checked = False
        Next
    End Sub

    Private Sub SelectAllButToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles SelectAllButToolStripMenuItem.Click
        For i As Integer = 0 To pList.Nodes.Count - 1
            If i <> pList.SelectedNode.Index Then pList.Nodes(i).Checked = True Else pList.Nodes(i).Checked = False
        Next
    End Sub

    Private Sub DeselectAllElseToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles DeselectAllElseToolStripMenuItem.Click
        For i As Integer = 0 To pList.Nodes.Count - 1
            If i <> pList.SelectedNode.Index Then pList.Nodes(i).Checked = False Else pList.Nodes(i).Checked = True
        Next
    End Sub

    Private Sub PartContextMenu_Opening(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles PartContextMenu.Opening
        AssnMt.DropDownItems.Clear()
        For i As Integer = 0 To loadedModels(0).ModelData.Materials.Length - 1
            AssnMt.DropDownItems.Add(loadedModels(0).ModelData.Materials(i).mtlId)
        Next
        If pList.Nodes.Count = 0 Then
            PartContextMenu.Items(2).Enabled = False
            PartContextMenu.Items(3).Enabled = False
        Else
            PartContextMenu.Items(2).Enabled = True
            PartContextMenu.Items(3).Enabled = True
        End If
        If IsNothing(pList.SelectedNode) Then
            PartContextMenu.Items(4).Enabled = False
            PartContextMenu.Items(5).Enabled = False
        Else
            If loadedModels(0).ModelData.Parts(pList.SelectedNode.Index).matInd > -1 Then
                PartContextMenu.Items(2).Enabled = True

            End If
            PartContextMenu.Items(4).Enabled = True
            PartContextMenu.Items(5).Enabled = True
        End If
    End Sub
    Private Sub NonecameraOnlyToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles NonecameraOnlyToolStripMenuItem.Click
        ChangeTool(MouseTools.NoTool)
    End Sub

    Private Sub PartSelectorToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles PartSelectorToolStripMenuItem.Click
        ChangeTool(MouseTools.PartSelector)
    End Sub

    Private Sub VertexSelectorToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles VertexSelectorToolStripMenuItem.Click
        ChangeTool(MouseTools.VertexSelector)
    End Sub

    Private Sub PolygonSelectorToolStripMenuItem_Click_1(sender As Object, e As EventArgs) Handles PolygonSelectorToolStripMenuItem.Click
        ChangeTool(MouseTools.PolygonSelector)
    End Sub

    Private Sub ModelSelectorToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles ModelSelectorToolStripMenuItem.Click
        ChangeTool(MouseTools.ModelSelector)
    End Sub
    Private Sub SetDirLock(ByVal ax As Axis)
        lockAxes = ax
        Dim n As String = ""
        Select Case ax
            Case Axis.BOTH
                n = "X + Y"
            Case Axis.NONE
                n = "NONE"
            Case Axis.X
                n = "X"
            Case Axis.Y
                n = "Y"
        End Select
        DirLabel.Text = "Movement Direction Lock: " & n
    End Sub
    Private Sub XYToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles xyLabel.Click
        SetDirLock(Axis.BOTH)
    End Sub

    Private Sub XToolStripMenuItem1_Click(sender As Object, e As EventArgs) Handles XToolStripMenuItem1.Click
        SetDirLock(Axis.X)
    End Sub

    Private Sub YToolStripMenuItem1_Click(sender As Object, e As EventArgs) Handles YToolStripMenuItem1.Click
        SetDirLock(Axis.Y)
    End Sub

    Private Sub NoneToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles NoneToolStripMenuItem.Click
        SetDirLock(Axis.NONE)
    End Sub
#End Region

    Private Sub pList_AfterCheck(sender As Object, e As TreeViewEventArgs) Handles pList.AfterCheck
        If e.Node.Level = 0 Then
            loadedModels(0).ModelData.Parts(e.Node.Index).skip = (Not e.Node.Checked)
        End If
    End Sub

    Private Sub DirLabel_Click(sender As Object, e As EventArgs) Handles DirLabel.Click

    End Sub

    Private Sub EditMaterialToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles EditMaterialToolStripMenuItem.Click
        If pList.SelectedNode.Index > -1 Then
            If loadedModels(0).ModelData.Parts(pList.SelectedNode.Index).matInd > -1 Then
                MatEditor = New MaterialEditor(loadedModels(0).matFN, loadedModels(0).ModelData.Parts(pList.SelectedNode.Index).matInd)
            Else
                Dim q As MsgBoxResult = MsgBox("Part has no material assigned. Create new?", MsgBoxStyle.YesNo, "No material exists")
                If q = MsgBoxResult.Yes Then
                    MatEditor = New MaterialEditor(loadedModels(0).matFN)
                End If
            End If
        End If
    End Sub

    Private Sub MaterialEditorToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles mtlEdBtn.Click
        MatEditor = New MaterialEditor()
    End Sub

    Private Sub AssnMt_DropDownItemClicked(sender As Object, e As ToolStripItemClickedEventArgs) Handles AssnMt.DropDownItemClicked
        loadedModels(0).ModelData.Parts(pList.SelectedNode.Index).matInd = AssnMt.DropDownItems.IndexOf(e.ClickedItem)
    End Sub

    Private Sub RenderOp_Tick(sender As Object, e As EventArgs) Handles RenderOp.Tick
        MainExecute()
    End Sub

    Private Sub CamSpeedBar_MouseDown(sender As Object, e As MouseEventArgs) Handles CamSpeedBar.MouseDown
        cSpB = True
        mP = Windows.Forms.Cursor.Position
        Windows.Forms.Cursor.Hide()
    End Sub

    Private Sub CamSpeedBar_MouseUp(sender As Object, e As MouseEventArgs) Handles CamSpeedBar.MouseUp
        cSpB = False
        Windows.Forms.Cursor.Position = mP
        Windows.Forms.Cursor.Show()
    End Sub

    Private Sub CamSpeedBar_MouseMove(sender As Object, e As MouseEventArgs) Handles CamSpeedBar.MouseMove
        If cSpB Then
            nP = Windows.Forms.Cursor.Position
            If nP.Y <> mP.Y Then
                If CamSpeedBar.Value - (nP.Y - mP.Y) <= CamSpeedBar.Minimum Then
                    CamSpeedBar.Value = CamSpeedBar.Minimum
                ElseIf CamSpeedBar.Value - (nP.Y - mP.Y) >= CamSpeedBar.Maximum Then
                    CamSpeedBar.Value = CamSpeedBar.Maximum
                    CamSpeedBar.Value -= 1
                    CamSpeedBar.Value += 1
                Else
                    CamSpeedBar.Value -= (nP.Y - mP.Y)
                    CamSpeedBar.Value -= 1
                    CamSpeedBar.Value += 1
                End If
            End If
            Camera.Speed = (CamSpeedBar.Value / 255.0F)
            nP = Windows.Forms.Cursor.Position
            Windows.Forms.Cursor.Position = mP
            Me.Update()
        End If
    End Sub
End Class
